﻿//------------------------------------------------------------------------------
// Redirect.cpp : implementation file
//
// Creates a child process that runs a user-specified command and redirects its
// standard output and standard error to a CEdit control.
//
// Written by Matt Brunk (brunk@gorge.net)
// Copyright (C) 1999 Matt Brunk
// All rights reserved.
//
// This code may be used in compiled form in any way. This file may be
// distributed by any means providing it is not sold for profit without
// the written consent of the author, and providing that this notice and the
// author's name is included in the distribution. If the compiled form of the
// source code in this file is used in a commercial application, an e-mail to
// the author would be appreciated.
//
// Thanks to Dima Shamroni (dima@abirnet.co.il) for providing the essential
// code for this class.
//
// Thanks to Chris Maunder (chrismaunder@codeguru.com) for the PeekAndPump()
// function (from his CProgressWnd class).
//
// Initial Release Feb 8, 1999
//------------------------------------------------------------------------------

// BLUEnLIVE : 내 맘대로 고쳤음. brunk님께 항상 감사
// 고친 내용
// 초기화 parameter가 바뀜
// CRedirect(LPCTSTR szCommand, int iOutputMode, void *pOut, LPCTSTR szCurrDir, LPCTSTR szProcessToKill, bool bStreamingMode)
// iOutputMode의 구성 : 0xab 형식의 값을 가지며,
// b는 mode, a는 b==2일 때에만 StatusBar 위치를 의미함
// b즉, iOutputMode & 0x0f의 의미는
// 0 : pOut는 CEdit*로 인식, 모든 결과 출력
// 1 : pOut는 CEdit*로 인식, 한 줄만 출력
// 2 : pOut는 CStatusBarCtrl*로 인식, 한 줄만 출력임
// 3 : pOut는 CStatic*으로 인식, 한 줄만 출력
// bStreamingMode가 false이면 한번 읽어들인 결과에 대해서만 처리
// 현재의 프로그램(MyLAME GUI)에서는 2만 사용됨
//
// 통째로 재정비: 2010.11.6
// Outputmode 없앰
// 무조건 CStatusBarCtrl *pStatusBar로만 보냄
// tcOutputFile이 NULL이 아닐 때는 출력을 파일로도 내보냄
// iTrans 에서 사용
// 버퍼링 데이터는 char, 일반 문자열은 TCHAR로 구분, 버퍼 기능 삭제가 핵심임
// 물론, 스트리밍모드 따윈 삭제

// ffmpeg으로 비디오를 압축할 때만 좀 다르게 동작하도록 수정: 2013.6.2
// 1. ffmpeg에서 비디오를 압축할 때는 0:41(01:12, 5%) 포맷으로 출력
// 2. 출력이 단순히 5.55% 형식일 때도 출력하도록 수정(norm.exe) / 이 부분 없앨 것

// file 또는 StatusBar로만 출력하도록 수정: 2013.6.27
// iOutputmode는 2/4만 받음. 나머지는 무시

#include "stdafx.h"
#include <string.h>
#include "Redirect.h"
#include <Tlhelp32.h>
#include "SmallUtilz.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

const int BUF_SIZE = (4096*8);
const int CRedirect::statusBarPos = 2;

CRedirect::CRedirect
(
    LPCTSTR			lpCommand,
	CStringArray	&csaProcessesToKill,
    CStatusBarCtrl	*pStatusBar,
    LPCTSTR			lpCurrentDirectory,
    LPCTSTR			lpForceMessage,
	int				iTotalVideoFrames,
    LPCTSTR			lpOutputfilename
)
{
	m_bStopped				= false;
	m_dwSleepMilliseconds	= 100;
	m_pStatusBar			= (CStatusBarCtrl*)pStatusBar;
	m_iStatusBarPos			= statusBarPos;
	m_lpCommand				= lpCommand;
	m_lpCurrentDirectory	= lpCurrentDirectory;
	m_pcsaProcessesToKill	= &csaProcessesToKill;
	m_lpForceMessage		= lpForceMessage;
	m_iTotalVideoFrames		= iTotalVideoFrames;
	m_lpOutputfilename		= lpOutputfilename;
}

CRedirect::~CRedirect() {}

void CRedirect::Run(double *dMaxVolume)
{
	if (dMaxVolume) {
		*dMaxVolume = 0.0;
	}

	dwTimeBegin = GetTickCount();

	HANDLE					PipeReadHandle;
	HANDLE					PipeWriteHandle;
	PROCESS_INFORMATION		ProcessInfo;
	SECURITY_ATTRIBUTES		SecurityAttributes;
	STARTUPINFO				StartupInfo;
	BOOL					Success;

	//--------------------------------------------------------------------------
	//	Zero the structures.
	//--------------------------------------------------------------------------
	ZeroMemory( &StartupInfo,			sizeof( StartupInfo ));
	ZeroMemory( &ProcessInfo,			sizeof( ProcessInfo ));
	ZeroMemory( &SecurityAttributes,	sizeof( SecurityAttributes ));

	//--------------------------------------------------------------------------
	//	Create a pipe for the child's STDOUT.
	//--------------------------------------------------------------------------
	SecurityAttributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
	SecurityAttributes.bInheritHandle       = TRUE;
	SecurityAttributes.lpSecurityDescriptor = NULL;

	Success = CreatePipe
	          (
	              &PipeReadHandle,		// address of variable for read handle
	              &PipeWriteHandle,		// address of variable for write handle
	              &SecurityAttributes,	// pointer to security attributes
	              0						// number of bytes reserved for pipe (use default size)
	          );

	if ( !Success ) {
		ShowLastError(_T("Error creating pipe"));
		return;
	}

	//--------------------------------------------------------------------------
	//	Set up members of STARTUPINFO structure.
	//--------------------------------------------------------------------------
	StartupInfo.cb           = sizeof(STARTUPINFO);
	StartupInfo.dwFlags      = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	StartupInfo.wShowWindow  = SW_HIDE;
	StartupInfo.hStdOutput   = PipeWriteHandle;
	StartupInfo.hStdError    = PipeWriteHandle;

	//----------------------------------------------------------------------------
	//	Create the child process.
	//----------------------------------------------------------------------------


	Success = CreateProcess
	          (
	              LPTSTR(m_lpCommand),	// command line
	              NULL,					// pointer to name of executable module
	              NULL,					// pointer to process security attributes
	              NULL,					// pointer to thread security attributes (use primary thread security attributes)
	              TRUE,					// inherit handles
	              BELOW_NORMAL_PRIORITY_CLASS,						// creation flags
	              NULL,					// pointer to new environment block (use parent's)
	              m_lpCurrentDirectory,	// pointer to current directory name
	              &StartupInfo,			// pointer to STARTUPINFO
	              &ProcessInfo			// pointer to PROCESS_INFORMATION
	          );

	if ( !Success ) {
		ShowLastError(_T("Error creating process"));
		return;
	}

	DWORD	BytesLeftThisMessage = 0;
	DWORD	NumBytesRead;
	char	PipeData[BUF_SIZE];
	DWORD	TotalBytesAvailable = 0;

	if (m_lpForceMessage) {
		MessageStreamTCHAR(m_lpForceMessage);
	}

	FILE *fOut = NULL;
	if (m_lpOutputfilename) {
		if (_tfopen_s(&fOut, m_lpOutputfilename, _T("wt")) || !fOut) return;
	}

	for ( ; ; ) {
		NumBytesRead = 0;

		Success = PeekNamedPipe
		          (
		              PipeReadHandle,			// handle to pipe to copy from
		              PipeData,					// pointer to data buffer
		              1,						// size, in bytes, of data buffer
		              &NumBytesRead,			// pointer to number of bytes read
		              &TotalBytesAvailable,		// pointer to total number of bytes available
		              &BytesLeftThisMessage		// pointer to unread bytes in this message
		          );

		if ( !Success ) {
			ShowLastError(_T("PeekNamedPipe failed"));
			break;
		}

		if ( NumBytesRead ) {
			Success = ReadFile
			          (
			              PipeReadHandle,	// handle to pipe to copy from
			              PipeData,			// address of buffer that receives data
			              BUF_SIZE - 1,		// number of bytes to read
			              &NumBytesRead,	// address of number of bytes read
			              NULL				// address of structure for data for overlapped I/O
			          );

			if ( !Success ) {
				ShowLastError(_T("ReadFile fialed"));
				break;
			}

			//------------------------------------------------------------------
			//	Zero-terminate the data.
			//------------------------------------------------------------------
			PipeData[NumBytesRead] = '\0';

			//------------------------------------------------------------------
			//	Replace backspaces with spaces.
			//------------------------------------------------------------------
			for ( DWORD ii = 0; ii < NumBytesRead; ii++ ) {
				if ( PipeData[ii] == _T('\b') ) {
					PipeData[ii] = ' ';
				}
			}

			//------------------------------------------------------------------
			//	If we're running a batch file that contains a pause command,
			//	assume it is the last output from the batch file and remove it.
			//------------------------------------------------------------------
			char  *ptr = strstr(PipeData, "Press any key to continue . . .");
			if ( ptr ) {
				*ptr = '\0';
			}

			/////////// 여기부터 메인 이벤트

			if (dMaxVolume) {
				// [Parsed_volumedetect_0 @ 0000000004753f20] max_volume: 0.0 dB 찾기
				// PipeData[NumBytesRead]에서 "] max_volume: 0.0 dB"를 찾기
				
				static DWORD i;
				static double d;

				for (i=0; i<NumBytesRead; i++) {
					if (PipeData[i] != ']') continue;
					if (strncmp(PipeData+i, "] max_volume: ", 14)) continue;
					d = atof(PipeData+i+14);

					i+=14;
					while (isdigit_char(PipeData[i]) || PipeData[i]=='-' ||PipeData[i]=='.') i++;
					if (strncmp(PipeData+i, " dB", 3)==0) {
						*dMaxVolume = d;
						break;
					}
				}
			}

			//------------------------------------------------------------------
			//	Append the output to the CStatusBar control.
			//------------------------------------------------------------------
			
			if (fOut) {
				fprintf(fOut, "%s", PipeData);
			}
			MessageStreamCHAR(PipeData);

			//------------------------------------------------------------------
			//	Peek and pump messages.
			//------------------------------------------------------------------
			PeekAndPump();
		} else {
			//------------------------------------------------------------------
			//	If the child process has completed, break out.
			//------------------------------------------------------------------
			if ( WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0 ) {	//lint !e1924 (warning about C-style cast)
				break;
			}

			//------------------------------------------------------------------
			//	Peek and pump messages.
			//------------------------------------------------------------------
			PeekAndPump();

			//------------------------------------------------------------------
			//	If the user canceled the operation, terminate the process.
			//------------------------------------------------------------------
			if ( m_bStopped ) {
				Success = TerminateProcess
				          (
				              ProcessInfo.hProcess,
				              0
				          );

				if ( Success ) {
					MessageStreamTCHAR(_T("Job Canceled. Process terminated successfully."));
				} else {
					ShowLastError(_T("Error terminating process."));
				}

				break;
			}

			//------------------------------------------------------------------
			//	Sleep.
			//------------------------------------------------------------------
			Sleep(m_dwSleepMilliseconds);
		}

	}

	if (fOut)
		fclose(fOut);

	if (!m_bStopped) {
		MessageStreamTCHAR(_T("Job Finished."));

		bJobCanceled = false;
	} else {
		bJobCanceled = true;
	}

	//--------------------------------------------------------------------------
	//	Close handles.
	//--------------------------------------------------------------------------
	
	CloseHandle(ProcessInfo.hThread);
	CloseHandle(ProcessInfo.hProcess);
	CloseHandle(PipeReadHandle);
	CloseHandle(PipeWriteHandle);

	int count = m_pcsaProcessesToKill->GetSize();
	for (int i=0; i<count; i++) {
		KillProcess(m_pcsaProcessesToKill->GetAt(i));
	}
}


void CRedirect::ShowLastError(LPCTSTR szText)
{
	LPVOID		lpMsgBuf;
	DWORD		Success;

	//--------------------------------------------------------------------------
	//	Get the system error message.
	//--------------------------------------------------------------------------
	Success = FormatMessage
	          (
	              FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
	              NULL,
	              GetLastError(),
	              MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),	//lint !e1924 (warning about C-style cast)
	              LPTSTR(&lpMsgBuf),
	              0,
	              NULL
	          );

	CString	Msg;

	Msg = szText;
	Msg += _T("\r\n");
	if ( Success ) {
		Msg += LPTSTR(lpMsgBuf);
	} else {
		Msg += _T("No status because FormatMessage failed.\r\n");
	}

	MessageStreamTCHAR(static_cast<LPCTSTR>(Msg));
}

void CRedirect::PeekAndPump()
{
	MSG Msg;
	while (::PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE))  {
		(void)AfxGetApp()->PumpMessage(); //lint !e1924 (warning about C-style cast)
	}
}

void CRedirect::Stop()
{
	m_bStopped = true;
}

void CRedirect::SetSleepInterval(DWORD dwMilliseconds)
{
	m_dwSleepMilliseconds = dwMilliseconds;
}

void CRedirect::KillProcess(LPCTSTR lpExeName)
{
	if (!lpExeName) return;
	if (!(*lpExeName)) return;

	HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (h == (HANDLE)-1) return;

	PROCESSENTRY32 pe32 = {0};
	pe32.dwSize = sizeof(PROCESSENTRY32);

	if (!Process32First(h, &pe32)) {
		CloseHandle(h);
		return;
	}

	BOOL b;
	do {
		if (_tcsicmp(lpExeName, pe32.szExeFile) == 0) {
			HANDLE hP = OpenProcess(PROCESS_TERMINATE, TRUE, pe32.th32ProcessID);
			if (hP) {
				TerminateProcess(hP, 0);
				CloseHandle(hP);
			}
		}
		b = Process32Next(h, &pe32);
	} while(b);

	CloseHandle(h);
}


/// 방향을 다시 잡았음
/// 1. 출력은 SB, CE, CS 똑같다
/// 2. 강제출력은 TCHAR, 스트림은 char
/// 3. 버퍼 처리는 의미 없음
/// 4. 강제출력 메쏘드와 스트림 메쏘드는 분리되어야 함

void CRedirect::MessageStreamCHAR(char * Text)
{
	static TCHAR tcB[MAX_PATH*2];
	int iPos;

	if (!m_pStatusBar || (m_pStatusBar->GetSafeHwnd() == NULL)) return;

	iPos = FindDisplayData(Text);
	if (CopyDisplayInfo(tcB, Text, strlen(Text), iPos) == 0)
		return;

	CString csB(tcB);
	if (csB.Left(10).Compare(_T("Processed ")) == 0) {
		int iP = csB.Find(_TCHAR(' '), 10);
		if (iP>=0)
			csB = csB.Left(iP)+_T(" seconds...");
		else
			return;
	}

	MessageStreamInternal(csB);
}

void CRedirect::MessageStreamTCHAR(LPCTSTR Text)
{
	//버퍼용이 아님. 그냥 뿌려주는 메쏘드
	if (!m_pStatusBar || (m_pStatusBar->GetSafeHwnd() == NULL)) return;

	MessageStreamInternal(Text);
}

void CRedirect::MessageStreamInternal(LPCTSTR lpText)
{
	// video encoding mode일 떄는
	// 0:41(01:12, 5%) / fps=26...
	// 맨 앞의 frame= 5569 부분을 제거해야 됨
	// 그 외에는
	// 0:00 / size= 951808kB time=02:22:19.98 bits...

	DWORD dwTickElapsed = GetTickCount()-dwTimeBegin;
	CString cs = tcGetElapedTime(dwTickElapsed, -1, -1.0);
	cs += lpText;
	
	/* 10초 이상 경과시에만 남은 시간을 계산 */
	if (dwTickElapsed>10000 && m_iTotalVideoFrames>0 && _tcsncmp(lpText, _T("frame="), 6)==0) {
		CString cs0;
		cs0=lpText;
		cs0.Delete(0, 6);
		cs0.TrimLeft();

		int frameNow = _ttoi(static_cast<LPCTSTR>(cs0));
		cs0.TrimLeft(_T("0123456789 "));

		cs = tcGetElapedTime(dwTickElapsed, frameNow, double(frameNow)/m_iTotalVideoFrames)+cs0;
	}

	m_pStatusBar->SetText(cs, m_iStatusBarPos, 0);
}

int CRedirect::FindDisplayData(char *cText)
{
	if (!cText) return 0;
	int iLen = strlen(cText);

	//달랑 숫자와 퍼센트로만 구성되었는지 확인 (14%) 이건 무조건 출력

	int iNumCount=0, iPercentCount=0, iElseCount=0;

	for (int i=0; i<iLen; i++) {
		char ch = cText[i];
		if (ch=='%')
			iPercentCount++;
		else if (isdigit_char(ch))
			iNumCount++;
		else if (!iscntrl_char(ch))
			iElseCount++;
	}

	if (iElseCount==0 && iPercentCount==1 && iNumCount) {
		return 0;
	}

	//이제부터 문자열 비교

	if (iLen<6) return -1;
	for (int i=iLen-5; i>=0; i--) {
		//00:00 찾기
		if ((isdigit_char(cText[i]) || (cText[i]==' ')) &&
		    isdigit_char(cText[i+1]) &&
		    (cText[i+2] == ':') &&
			(isdigit_char(cText[i+3]) || (cText[i+3]==' ')) &&
		    isdigit_char(cText[i+4])
		   )
			return i;

		//.00% 찾기
		if ((cText[i] == '.') &&
			isdigit_char(cText[i+1]) &&
			isdigit_char(cText[i+2]) &&
			(cText[i+3] == '%'))
			return i;

		//0/00 찾기
		if (isdigit_char(cText[i]) &&
			(cText[i+1] == '/') &&
			isdigit_char(cText[i+2]) &&
			isdigit_char(cText[i+3])
			)
			return i;

		static LPCSTR lpSixChars[] = {
			"mixing",	" time=",	"Frame#",	"essed ",
			"ogress",	"mporti",	"riting"
		};

		for (int j=0; j<sizeof(lpSixChars)/sizeof(lpSixChars[0]); j++) {
			if (strncmp(cText+i, lpSixChars[j], 6) == 0)
				return i;
		}
	}

	return iLen-1;
}

int CRedirect::CopyDisplayInfo(TCHAR *tcTo, char *cFrom, int iSize, int iPos)
{
	int iBegin = iPos;
	while ((iBegin>0) &&
	        !isspecialornull(cFrom[iBegin])
	      )
		iBegin--;

	while ((iBegin<iSize) &&
	        isspecialornull(cFrom[iBegin])
	      )
		iBegin++;

	int iEnd = iPos;
	while ((iEnd<iSize) &&
	        !isspecialornull(cFrom[iEnd])
	      )
		iEnd++;

	while ((iEnd>0) &&
	        isspecialornull(cFrom[iEnd])
	      )
		iEnd--;

	if (iEnd<iBegin) {
		//문자열이 없을 떄
		tcTo[0] = _TCHAR(0);
		return 0;
	}

	int iTo=0;
	for (int i=iBegin; i<=iEnd; i++) {
		tcTo[iTo++] = (TCHAR)(cFrom[i]);
	}
	tcTo[iTo] = _TCHAR(0);

	return iEnd-iBegin+1;
}


TCHAR * CRedirect::tcGetElapedTime(DWORD dwTimeElapsed, int currentframe, double ratio)
{
	// 12:00:00
	// 01234567
	//static TCHAR tcTimeStr[13] = {0};

	// 0:41(01:12, 5%)
	// 형식으로 포맷 변경
	// 00:00:00 (00:00:00, 100%)
	// 012345678901234567890123456789
	static TCHAR tcTimeStr[40] = {0};

	if (ratio<=0.0 || ratio>100.0) {
		int H, M, S, MS;
		CSmallUtilz4iTrans::ms2HMSMS(int(dwTimeElapsed), H, M, S, MS);
		
		if (H) {
			_stprintf_s(tcTimeStr, 40, _T("%d:%02d:%02d / "), H, M, S);
		} else {
			_stprintf_s(tcTimeStr, 40, _T("%d:%02d / "), M, S);
		}
	} else {
		static TCHAR timestrTemp[13] = {0};

		int H, M, S, MS;
		CSmallUtilz4iTrans::ms2HMSMS(int(dwTimeElapsed), H, M, S, MS);
		if (H) {
			_stprintf_s(timestrTemp, 40, _T("%d:%02d:%02d"), H, M, S);
		} else {
			_stprintf_s(timestrTemp, 40, _T("%d:%02d"), M, S);
		}

		// dwTimeElapsed 재활용
		dwTimeElapsed = DWORD(dwTimeElapsed/ratio - dwTimeElapsed);
		CSmallUtilz4iTrans::ms2HMSMS(int(dwTimeElapsed), H, M, S, MS);
		if (H) {
			_stprintf_s(tcTimeStr, 40, _T("%s(%d:%02d:%02d, %d/%.1f%%), "), timestrTemp, H, M, S, currentframe, ratio*100);
		} else {
			_stprintf_s(tcTimeStr, 40, _T("%s(%d:%02d, %d/%.1f%%), "), timestrTemp, M, S, currentframe, ratio*100);
		}
	}
	
	return tcTimeStr;
}
